package http

import (
	"fmt"
	"gitee.com/captials-team/ubdframe/src/common/utils"
	"gitee.com/captials-team/ubdframe/src/domain/configstc"
	"gitee.com/captials-team/ubdframe/src/domain/converts"
	"gitee.com/captials-team/ubdframe/src/domain/dto"
	"gitee.com/captials-team/ubdframe/src/domain/dto/reqdata"
	"gitee.com/captials-team/ubdframe/src/domain/dto/respdata"
	"gitee.com/captials-team/ubdframe/src/domain/interfaces"
	"gitee.com/captials-team/ubdframe/src/domain/models"
	"gitee.com/captials-team/ubdframe/src/pkg/gin_http"
	"gitee.com/captials-team/ubdframe/src/pkg/i18n"
	"gitee.com/captials-team/ubdframe/src/pkg/jwtauth"
	v1log "gitee.com/captials-team/ubdframe/src/pkg/logs"
	"gitee.com/captials-team/ubdframe/src/pkg/passwd"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/silenceper/wechat/v2"
	"github.com/silenceper/wechat/v2/cache"
	"github.com/silenceper/wechat/v2/miniprogram"
	miniConfig "github.com/silenceper/wechat/v2/miniprogram/config"
	"golang.org/x/text/language"
	"gorm.io/gorm"
	"net/http"
	"time"
)

type AuthController struct {
	l              v1log.ILog
	userDao        interfaces.ItfUser
	userThirdDao   interfaces.ItfUserThird
	loginRecordDao interfaces.ItfUserLoginRecord
	verifyCodeDao  interfaces.ItfVerifyCode

	jwtAuth interfaces.ItfJwtParser

	conf *configstc.UserAppConfig

	wxMiniProg *miniprogram.MiniProgram //微信miniProg支持

	*gin_http.I18nController
	passwd.SaltMd5Password
}

func NewAuthController(conf *configstc.UserAppConfig, l v1log.ILog, userDao interfaces.ItfUser, userThirdDao interfaces.ItfUserThird, loginRecordDao interfaces.ItfUserLoginRecord, verifyCodeDao interfaces.ItfVerifyCode) *AuthController {
	ctr := &AuthController{
		l:               l,
		conf:            conf,
		jwtAuth:         jwtauth.NewJwtTokenHandler(conf.AuthConfig.SecretKey),
		SaltMd5Password: passwd.SaltMd5Password{Salt: conf.PasswordSalt},
	}
	//i18n
	{
		parser := i18n.NewParser()
		//parser.Bundle().MustParseMessageFileBytes([]byte(I18nEn), language.English.String()+".toml")
		//parser.Bundle().MustParseMessageFileBytes([]byte(I18nZh), language.Chinese.String()+".toml")
		//for name, path := range conf.I18nFiles {
		//	parser.Load(path, name+".toml")
		//	l.Info("load i18n path %s: %s, %s", name, path, parser.Lang(name).ParseMsg("Hello"))
		//}
		parser.SetLanguage(language.Chinese)
		parser.CacheLanguages()
		ctr.I18nController = &gin_http.I18nController{
			Parser: i18n.NewParser(),
		}
	}

	//support we chat
	if conf.WechatAuth.Enable {
		wc := wechat.NewWechat()
		memory := cache.NewMemory()
		cfg := &miniConfig.Config{
			AppID:     conf.WechatAuth.AppKey,
			AppSecret: conf.WechatAuth.AppSecret,
			Cache:     memory,
		}
		ctr.wxMiniProg = wc.GetMiniProgram(cfg)
	}

	ctr.userDao = userDao
	ctr.userThirdDao = userThirdDao
	ctr.loginRecordDao = loginRecordDao
	ctr.verifyCodeDao = verifyCodeDao

	return ctr
}

// SignUp godoc
// @Summary      注册账户
// @Description  注册账户（账户/密码）
// @Tags         user.auth
// @Accept       json
// @Produce      json
// @Param        param    body     reqdata.RegisterReq true  "注册信息"
// @success 0  {object} respdata.ResponseData{} "注册成功"
// @success 500 {object} respdata.ResponseData{} "授权失败"
// @Router       /auth/register [post]
func (ctr *AuthController) SignUp(ctx *gin.Context) {
	if !ctr.conf.AuthRegister {
		ctx.JSON(http.StatusOK, respdata.CNotSupport)
		return
	}
	var req reqdata.RegisterReq
	ctx.ShouldBindJSON(&req)

	if req.Account == "" || req.Password == "" {
		ctx.JSON(http.StatusOK, respdata.CParamsInvalid)
		return
	}
	//验证码为手机号/邮箱发送的验证码
	if req.Target() == "" || req.Code == "" {
		ctx.JSON(http.StatusOK, respdata.CParamsInvalid)
		return
	}
	//校验验证码
	ok, err := ctr.verifyCodeDao.Verify(req.Target(), req.Code)
	if err != nil || !ok {
		ctr.l.Ctl(err != nil).Error("verify err %s", err)
		ctx.JSON(http.StatusOK, respdata.CVtCodeInvalid)
		return
	}
	if req.Nickname == "" {
		req.Nickname = utils.RandNickname()
	}

	salt := utils.RandLetterFigureCode(12)
	user := &models.User{
		Account:   req.Account,
		Password:  ctr.GenPassword(req.Password, salt),
		Salt:      salt,
		Nickname:  req.Nickname,
		Email:     req.Email,
		Phone:     req.Phone,
		LastLogin: nil,
		LastIp:    "",
		Status:    1,
	}

	//检查账户是否存在
	if find, _ := ctr.userDao.QueryByAccount(user.Account); find != nil {
		ctx.JSON(http.StatusOK, respdata.CError.MMsg("account_exists"))
		return
	}

	_, err = ctr.userDao.Add(user)
	if err != nil {
		ctr.l.Ctl(err != nil).Error("add new user err %s", err)
		ctr.Response(ctx, respdata.CTryAgain)
		return
	}

	ctx.JSON(http.StatusOK, respdata.CSuccess.MData(respdata.RegisterResp{
		Account:  user.Account,
		Nickname: user.Nickname,
		Sex:      user.Sex,
		Avatar:   user.Avatar,
		Email:    user.Email,
		Phone:    user.Phone,
	}))
	return
}

// AuthLogin godoc
// @Summary      Auth-授权登录
// @Description  登录
// @Tags         user.auth
// @Produce      json
// @Param        param    body     reqdata.AuthReq  true  "账户信息"
// @success 200  {object} respdata.ResponseData{data=respdata.AuthResp} "授权成功"
// @success 500 {object} respdata.ResponseData{} "授权失败"
// @Router       /auth/login [post]
// @x-response-apikey-token-set {"tokenKey":"response.data.token","apiKey":"ApiKeyAuth"}
func (ctr *AuthController) AuthLogin(ctx *gin.Context) {
	var req reqdata.AuthReq

	ctx.ShouldBindBodyWith(&req, binding.JSON)
	if req.Account == "" || req.Password == "" {
		ctx.JSON(http.StatusOK, respdata.CParamsInvalid)
		return
	}
	if req.CaptchaKey == "" || req.CaptchaCode == "" {
		ctx.JSON(http.StatusOK, respdata.CParamsInvalid)
		return
	}
	//ctr.l.Debug("auth req %v", req)

	user, err := ctr.userDao.QueryByAccount(req.Account)
	if err != nil {
		ctr.l.Error("query err %s", err)
		ctx.JSON(http.StatusOK, respdata.CError)
		return
	}
	if user == nil || user.Password != ctr.GenPassword(req.Password, user.Salt) {
		ctx.JSON(http.StatusOK, respdata.CError.MMsg("account/password wrong"))
		return
	}

	//登录记录
	ctr.loginRecord(ctx, ctr.user2Auth(user))

	ctr.authResponse(ctx, ctr.user2Auth(user))
}

// AuthLogout godoc
// @Summary      Auth-取消授权
// @Description  取消授权
// @Tags         user.auth
// @Produce      json
// @Security     ApiKeyAuth
// @success 200  {object} respdata.ResponseData{} "取消成功"
// @success 500 {object} respdata.ResponseData{} "取消失败"
// @Router       /auth/logout [post]
func (ctr *AuthController) AuthLogout(ctx *gin.Context) {
	ctx.JSON(http.StatusOK, respdata.CSuccess)
}

// AuthFreshToken godoc
// @Summary      Auth-刷新授权
// @Description  刷新授权token
// @Tags         user.auth
// @Produce      json
// @Security     ApiKeyAuth
// @success 200  {object} respdata.ResponseData{data=respdata.AuthResp} "刷新成功"
// @success 500 {object} respdata.ResponseData{} "刷新失败"
// @Router       /auth/fresh_token [post]
// @x-response-apikey-token-set {"tokenKey":"response.data.token","apiKey":"ApiKeyAuth"}
func (ctr *AuthController) AuthFreshToken(ctx *gin.Context) {
	auth := gin_http.GetAuth(ctx)
	ctr.authResponse(ctx, auth)
}

// AuthByWxCode godoc
// @Summary      Auth-微信code授权
// @Description  第三方登录-微信
// @Tags         user.auth
// @Produce      json
// @Param        param    body     reqdata.ThirdAuthReq  true  "第三方授权码"
// @success 200  {object} respdata.ResponseData{data=respdata.AuthResp} "授权成功"
// @success 500 {object} respdata.ResponseData{} "授权失败"
// @Router       /auth/wechat_code [post]
// @x-response-apikey-token-set {"tokenKey":"response.data.token","apiKey":"ApiKeyAuth"}
func (ctr *AuthController) AuthByWxCode(ctx *gin.Context) {
	if ctr.wxMiniProg == nil {
		ctx.JSON(http.StatusOK, respdata.CNotSupport)
		return
	}
	var req reqdata.ThirdAuthReq

	ctx.ShouldBind(&req)
	if req.Code == "" {
		ctx.JSON(http.StatusOK, respdata.CParamsInvalid.MMsg("code is empty"))
		return
	}

	//mini program授权码授权
	authSession, err := ctr.wxMiniProg.GetAuth().Code2Session(req.Code)
	if err != nil {
		ctr.l.Error("wechat err: %s", err)
		ctx.JSON(http.StatusOK, respdata.CError.MMsg("AUTH_FAIL"))
		return
	}

	thirdInfo := &models.UserThird{
		ThirdId:      authSession.OpenID,
		ThirdUnionId: authSession.UnionID,
		ThirdType:    models.ThirdWechat,
	}
	ctr.thirdAuth(ctx, thirdInfo)
}

// AuthByQQ godoc
// @Summary      Auth-QQ授权登录
// @Description  第三方登录-QQ
// @Tags         user.auth
// @Produce      json
// @Param        param    body     reqdata.ThirdAuthReq  true  "第三方授权码"
// @success 200  {object} respdata.ResponseData{data=respdata.AuthResp} "授权成功"
// @success 500 {object} respdata.ResponseData{} "授权失败"
// @Router       /auth/qq_code [post]
// @x-response-apikey-token-set {"tokenKey":"response.data.token","apiKey":"ApiKeyAuth"}
func (ctr *AuthController) AuthByQQ(ctx *gin.Context) {
	ctr.Response(ctx, respdata.CNotSupport)
}

// AuthInfo godoc
// @Summary      Auth-登录信息
// @Description  获取登录信息
// @Tags         user.auth
// @Security     ApiKeyAuth
// @Produce      json
// @success 200  {object} respdata.ResponseData{data=respdata.AuthResp} "授权信息"
// @Router       /auth/info [post]
func (ctr *AuthController) AuthInfo(ctx *gin.Context) {
	auth := gin_http.GetAuthDataPacket(ctx)
	ctr.l.Debug("auth %d-%s", auth.Auth.Id, auth.Auth.Nickname)

	auth.Token = ""

	ctr.Response(ctx, respdata.CSuccess.MData(utils.CopyTo(&auth, new(respdata.AuthResp))))
}

// AuthCheck godoc
// @Summary      Auth-登录校验
// @Description  登录校验ok正常返回，不通过返回99
// @Tags         user.auth
// @Security     ApiKeyAuth
// @Produce      json
// @success 200  {object} respdata.ResponseData{} "返回结果"
// @Router       /auth/check [post]
func (ctr *AuthController) AuthCheck(ctx *gin.Context) {
	ctr.Response(ctx, respdata.CSuccess)
}

// DebugAuthByThird godoc
// @Summary      [DEBUG]第三方授权
// @Description  第三方登录-微信
// @Tags         user.auth
// @Produce      json
// @success 200  {object} respdata.ResponseData{data=respdata.AuthResp} "授权成功"
// @success 500 {object} respdata.ResponseData{code} "授权失败"
// @Router       /debug/auth/third [post]
// @x-response-apikey-token-set {"tokenKey":"response.data.token","apiKey":"ApiKeyAuth"}
func (ctr *AuthController) DebugAuthByThird(ctx *gin.Context) {
	ctr.l.Debug("debug auth third")

	thirdInfo := &models.UserThird{
		ThirdId:   fmt.Sprintf("debug_user_%s", time.Now().Format("200601021504")),
		ThirdType: models.ThirdWechat,
		Nickname:  utils.RandNickname(),
		Phone:     fmt.Sprintf("1%d%s", utils.TrueScopeRand(33, 88), utils.RandFigureCode(8)),
		Email:     fmt.Sprintf("%s@qq%s.com", utils.RandFigureCode(6), utils.RandFigureCode(3)),
	}
	thirdInfo.ThirdUnionId = thirdInfo.ThirdId

	ctr.thirdAuth(ctx, thirdInfo)
}

// thirdAuth 第三方授权处理
func (ctr *AuthController) thirdAuth(ctx *gin.Context, thirdInfo *models.UserThird) {
	var user *models.User

	//先查询用户
	userWe, err := ctr.userThirdDao.QueryByThirdId(thirdInfo.ThirdType, thirdInfo.ThirdId)
	if err != nil {
		ctr.l.Error("query err: %s", err)
		ctr.Response(ctx, respdata.CSysBusy)
		return
	}
	//已存在则查询用户
	if userWe != nil {
		user, err = ctr.userDao.Query(userWe.UId)
		if err != nil {
			ctr.l.Error("query err: %s", err)
			ctx.JSON(http.StatusOK, respdata.CSysBusy)
			return
		}
		if user != nil {
			ctr.authResponse(ctx, ctr.user2Auth(user))
			return
		}
		//对应用户不存在则重新删除后进行重新创建的流程
		ctr.userThirdDao.Delete(userWe.Id)
	}

	//创建新用户
	db := ctr.userDao.DB()
	err = db.Transaction(func(tx *gorm.DB) error {
		//创建新用户
		user = thirdInfo.ConvertUser()

		uid, err := ctr.userDao.Use(tx).Add(user)
		if err != nil {
			return err
		}
		thirdInfo.UId = uid
		_, err = ctr.userThirdDao.Use(tx).Add(thirdInfo)
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		ctr.l.Error("new user fail err: %s", err)
		ctx.JSON(http.StatusOK, respdata.CSysBusy)
		return
	}

	ctr.authResponse(ctx, ctr.user2Auth(user))
}

// authResponse 授权返回
func (ctr *AuthController) authResponse(ctx *gin.Context, auth dto.AuthInfo) {
	if auth.Id <= 0 {
		ctx.JSON(http.StatusOK, respdata.CError.MMsg("auth err"))
		return
	}

	authData, err := converts.AuthToDataPacket(auth, time.Duration(ctr.conf.AuthConfig.AuthExpired)*time.Second, ctr.jwtAuth)
	if err != nil {
		ctr.l.Error("token fail %s", err)
		ctx.JSON(http.StatusOK, respdata.CError.MMsg("token err"))
		return
	}

	ctr.loginRecord(ctx, auth)

	ctr.Response(ctx, respdata.CSuccess.MData(utils.CopyTo(&authData, new(respdata.AuthResp))))
}

// user2Auth 用户转为model数据
func (ctr *AuthController) user2Auth(data *models.User) dto.AuthInfo {
	if data == nil {
		return dto.AuthInfo{}
	}
	if data.LastLogin == nil {
		t := time.Now()
		data.LastLogin = &t
	}
	return dto.AuthInfo{
		Id:       data.Id,
		Nickname: data.Nickname,
		Account:  data.Account,
		//Avatar:    data.Avatar,
		Status: data.Status,
		//Email:     data.Email,
		//Phone:     data.Phone,
		//LastLogin: data.LastLogin.Format(time.RFC3339),
		//LastIp:    data.LastIp,
		SaasId: 0,
	}
}

// loginRecord 登录记录
func (ctr *AuthController) loginRecord(ctx *gin.Context, auth dto.AuthInfo) {
	//登录记录
	go func(p *dto.LoginInfoParam) {
		ctr.userDao.LoginNote(auth.Id, p)
		ctr.loginRecordDao.Add(&models.UserLoginRecord{
			Uid:        auth.Id,
			LastDevice: p.Device,
			LastLogin:  &p.Time,
			LastLocate: "",
			LastIp:     p.Ip,
			Remark:     "",
		})
	}(&dto.LoginInfoParam{
		Time:   time.Now(),
		Ip:     ctx.ClientIP(),
		Device: ctx.Request.Header.Get("User-Agent"),
	})
}
